// Copyright 2010-2012, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_MTELSFILE_H_
#define LIBV_MTELSFILE_H_


// Отключение внутренних блокировок stdio.
// Будет использована однопоточная версия.
// Это несколько ускорит выполнение, при той же потокобезопасности,
// обеспечиваемой блокировками AMEFile.
#if defined(_WIN32) && !defined(_CRT_DISABLE_PERFCRIT_LOCKS)
# define _CRT_DISABLE_PERFCRIT_LOCKS
# define UNDEF_CRT_DISABLE_PERFCRIT_LOCKS
#endif

#include <algorithm>
#include <string>
#include <sstream>
#include <stdexcept>

#include <libv/check.h>
#include <libv/old_lsfile.h>
#include <libv/lock_mutex.h>
#include <libv/error_msg.h>
#include <libv/stdint.h>
#include <libv/suppress.h>
#include <libv/uncopyable.h>

#ifdef UNDEF_CRT_DISABLE_PERFCRIT_LOCKS
# undef _CRT_DISABLE_PERFCRIT_LOCKS
# undef UNDEF_CRT_DISABLE_PERFCRIT_LOCKS
#endif



namespace libv
{



/** Базовое исключение для всех ошибок, возникающих при работе с файлами. */
LIBV_DEFINE_SIMPLE_EXCEPTION(AMEFileException)

/** Исключение, генерируемое при возникновении ошибки ввода/вывода в файл. */
LIBV_DEFINE_DERIVED_EXCEPTION(IOException, AMEFileException)

/** Исключение, генерируемое при ошибке взятия блокировки файла на уровне ОС. */
LIBV_DEFINE_DERIVED_EXCEPTION(FileLockException, AMEFileException)



/** Потокобезопасная обертка над LSFILE,
  * генерирующая 3 типа исключений по кодам ошибок:
  *   - IOException, при ошибке ввода/вывода
  *   - libv::PrecondException, при вызове метода с некорректными аргументами
  *   - FileLockException, при ошибке блокировки файла на уровне ОС. */
class AMEFile : libv::Uncopyable
{
  public:
    typedef libv::LSFILE::LockType LockType;
    typedef libv::LSFILE::PreallocType PreallocType;

    void Create(const ::std::string& filepath, int mode = 0600)
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.Create(filepath.c_str(), mode), "Create");
    }


    void Open(const ::std::string& filepath)
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.Open(filepath.c_str()), "Open");
    }


    void Open(const ::std::string& filepath) const
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.Open(filepath.c_str()), "Open");
    }


    bool is_opened() const
    {
        libv::Lock dnd(&mutex_);
        return lsfile_.is_opened();
    }


    void Resize(const fpos64 max_off)
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.Resize(max_off), "Resize");
    }


    void Close()
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.Close(), "Close");
    }


    void Close() const
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.Close(), "Close");
    }


    void Write(fpos64* offset, const void* data, const size_t data_size)
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.Write(offset, data, data_size), "Write");
    }


    void Read(fpos64* offset, void* buffer, const size_t buffer_size) const
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.Read(offset, buffer, buffer_size), "Read");
    }


    fpos64 Size() const
    {
        libv::Lock dnd(&mutex_);
        const fpos64 r = lsfile_.Size();
        CheckForError(r, "Size");
        return r;
    }


    void Flush()
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.Flush(), "Flush");
    }


    void SetAutoRemove(const bool on)
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.SetAutoRemove(on), "SetAutoRemove");
    }


    void SetPreallocType(const int type, const fsize p_param)
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.SetPreallocType(type, p_param),"SetPreallocType");
    }


    bool TrySharedLock() const
    {
        libv::Lock dnd(&mutex_);
        const int r = lsfile_.TrySharedLock();

        if ( r == 0 ) return true;
        else if ( r == -4 ) return false;

        CheckForError(r, "TrySharedLock");

        LIBV_SUPPRESS_INTEL_MISSING_RETURN(false);
    }


    bool TryExclusiveLock() const
    {
        libv::Lock dnd(&mutex_);
        const int r = lsfile_.TryExclusiveLock();

        if ( r == 0 ) return true;
        else if ( r == -4 ) return false;

        CheckForError(r, "TryExclusiveLock");

        LIBV_SUPPRESS_INTEL_MISSING_RETURN(false);
    }


    void SharedLock() const
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.SharedLock(), "SharedLock");
    }


    void ExclusiveLock() const
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.ExclusiveLock(), "ExclusiveLock");
    }


    void Unlock() const
    {
        libv::Lock dnd(&mutex_);
        CheckForError(lsfile_.Unlock(), "Unlock");
    }


    int lock_type() const
    {
        libv::Lock dnd(&mutex_);
        return lsfile_.lock_type();
    }


    ::std::string file_path() const
    {
        libv::Lock dnd(&mutex_);
        return lsfile_.file_path();
    }



  private:
    /** Функция сразу завершается для неотрицательных значений.
      * Для отрицательных значений генерируется исключение с текстовым описанием
      * ошибки по коду LSFILE::last_error(). */
    inline void CheckForError(const int64_t method_return_value,
                              const ::std::string& method_name) const
    {
        // Size возвращает неошибочный результат, больший 0.
        if ( method_return_value >= 0 ) return;

        const ::std::string msg = method_name + " has failed: " +
                                  StrError(lsfile_.last_error());

        switch ( method_return_value )
        {
            // Внутренняя ошибка в SFILE.
            case -1: throw IOException(msg);

            // Некорректные аргументы в SFILE или LSFILE.
            case -2: PRECOND_MSG(false, method_name.c_str());

            // Ошибка взятия блокировки на файл в LSFILE.
            case -3: throw FileLockException(msg);

            default: throw AMEFileException("Incorrect method_return_value @ "
                                            "AMEFile::ErrorHandler.");
        }
    }


    libv::LSFILE lsfile_;

    // Warning! Эта блокировка обеспечивает корректную работу однопоточной
    // версии stdio, включенной макросом.
    mutable libv::Mutex mutex_;
};

}  // libv


// Временное решение
using libv::AMEFile;
using libv::AMEFileException;
using libv::IOException;
using libv::FileLockException;

#endif  // LIBV_MTELSFILE_H_
